package com.cw.utils.java.caffeine;

import com.github.benmanes.caffeine.cache.*;
import com.github.benmanes.caffeine.cache.stats.CacheStats;

import java.util.List;
import java.util.concurrent.TimeUnit;

//Eviction（驱除策略）
public class Eviction {

    //1.Size-based（基于容量）
    private static void sizeBased() {
        // 基于缓存内元素的个数，尝试回收最近或者未经常使用的元素
        LoadingCache<String, String> values = Caffeine.newBuilder()
                .maximumSize(10_000)
                .build(key -> create(key));
        // 也可以基于缓存元素的权重，进行驱除
        LoadingCache<String, String> graphs = Caffeine.newBuilder()
                .maximumWeight(10_000)
                .weigher((String key, String value) -> value.length())
                .build(key -> create(key));
    }

    //2.Time-based（基于时间）
    private static void timeBased() {
        // 自上一次写入或者读取缓存开始，在经过指定时间之后过期。
        LoadingCache<String, String> fixedAccess = Caffeine.newBuilder()
                .expireAfterAccess(5, TimeUnit.MINUTES)
                .build(key -> create(key));
        // 自缓存生成后，经过指定时间或者一次替换值之后过期。
        LoadingCache<String, String> fixedWrite = Caffeine.newBuilder()
                .expireAfterWrite(5, TimeUnit.MINUTES)
                .build(key -> create(key));
        // 自定义缓存过期策略，可以在创建时，写入后、读取时。
        LoadingCache<String, String> varying = Caffeine.newBuilder()
                .expireAfter(new Expiry<String, String>() {
                    public long expireAfterCreate(String key, String value, long currentTime) {
                        return currentTime;
                    }
                    public long expireAfterUpdate(String key, String value,
                                                  long currentTime, long currentDuration) {
                        return currentDuration;
                    }
                    public long expireAfterRead(String key, String value,
                                                long currentTime, long currentDuration) {
                        return currentDuration;
                    }
                })
                .build(key -> create(key));
    }

    //3.Reference-based（基于引用）
    private static void referenceBased() {
        // 当缓存key和value都不存在强引用关系时，进行驱逐
        LoadingCache<String, String> weak = Caffeine.newBuilder()
                .weakKeys()
                .weakValues()
                .build(key -> create(key));
        // 当发生GC时进行驱逐
        LoadingCache<String, String> soft = Caffeine.newBuilder()
                .softValues()
                .build(key -> create(key));
    }

    //三、Explicit Removals（显示删除）
    private static void explicitRemovals(){
        Cache<String, String> cache = Caffeine.newBuilder()
                .expireAfterWrite(10, TimeUnit.MINUTES)
                .maximumSize(10_000)
                .build((key -> create(key)));
        String key="";
        List<String> keys = null;
        // 直接删除
        cache.invalidate(key);
        // 批量删除
        cache.invalidateAll(keys);
        // 删除所有
        cache.invalidateAll();
    }

    //2.可监听删除
    //监听缓存元素被删除或者被驱除事件
    private static void removalsListeners() {
        Cache<String, String> cache = Caffeine.newBuilder()
                // 注意：evictionListener是3.X版本中新特性，2.X版本中没有
                .evictionListener((String key, String value, RemovalCause cause) ->
                        System.out.printf("Key %s was removed (%s)%n", key, cause))
                .removalListener((String key, String value, RemovalCause cause) ->
                        System.out.printf("Key %s was removed (%s)%n", key, cause))
                .build();
        cache.put("Hello", "Caffeine");
        System.out.println(cache.getIfPresent("Hello"));
        cache.invalidate("Hello");
        try {
            // 监听是异步执行的
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }


    //四、Refresh（刷新机制）
    //refresh只有在LoadingCache或者AsyncLoadingCache时才能使用，与驱逐不同之处，在刷新的时候，如果访问元素仍然可以返回，但返回的是旧值。
    static StringBuffer stringBuffer = new StringBuffer(">");
    private static void refresh() {
        LoadingCache<String, String> cache = Caffeine.newBuilder()
                .maximumSize(10_000)
                .refreshAfterWrite(1, TimeUnit.SECONDS)
                .build(key -> stringBuffer.append(key).toString());
        for (int i = 0; i < 5; i++) {
            // 这里需要注意，前两次输出都是：“>*”
            // 理论上第二次输出应是：“>**”，这是因为refreshAfterWrite刷新实际上指的是在x秒、并且是第二次访问之后才开始刷新
            System.out.println(cache.get("*"));
            try {
                Thread.sleep(1200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    //五、Statistics（统计）
    //caffeine提供了完善的缓存统计能力，提供了metrics-caffeine类库，可以直接入Prometheus
    private static void statistics() {
        Cache<String, String> cache = Caffeine.newBuilder()
                .maximumSize(10_000)
                .recordStats()
                .build();
        cache.put("Hello", "Caffeine");
        for (int i = 0; i < 15; i++) {
            // 命中15次
            cache.getIfPresent("Hello");
        }
        for (int i = 0; i < 5; i++) {
            // 未命中5次
            cache.getIfPresent("a");
        }
        cache.get("b", key -> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return key + "B";
        });
        CacheStats stats = cache.stats();
        System.out.println(stats);
        System.out.println("命中率：" + stats.hitRate());
        System.out.println("未命中率：" + stats.missRate());
        System.out.println("加载新值花费的平均时间：" + stats.averageLoadPenalty());
    }




    private static String create(Object key) {
        return key + " world";
    }
}
